﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Threading;
using System.IO;
using MediaServer.Utilities;

namespace MediaServer
{
    public class FileServer
    {
        private static readonly Dictionary<string, string> MIMETypes = new Dictionary<string, string>
        {
            {".bmp", "image/bmp"},
            {".gif", "image/gif"},
            {".ico", "image/x-icon"},
            {".jpeg", "image/jpeg"},
            {".jpg", "image/jpeg"},
            {".mid", "audio/midi"},
            {".midi", "audio/midi"},
            {".mov", "video/quicktime"},
            {".movie", "video/x-sgi-movie"},
            {".mp3", "audio/mpeg"},
            {".mp4", "video/mp4"},
            {".mpeg", "video/mpeg"},
            {".mpg", "video/mpeg"},
            {".ogg", "application/ogg"},
            {".png", "image/png"},
            {".wav", "audio/x-wav"}
        };

        private HttpListener lst;
        private Thread handler;
        private ILogger logger;
        private VirtualDirectory root;

        public FileServer(int port, ILogger logger, VirtualDirectory root)
        {
            this.logger = logger;
            this.root = root;
            lst = new HttpListener();
            lst.Prefixes.Add(String.Format("http://*:{0}/", port));
        }

        private string GetMIMEType(string path) 
        {
            FileInfo fi = new FileInfo(path);
            if (MIMETypes.ContainsKey(fi.Extension))
            {
                return MIMETypes[fi.Extension];
            }
            else
            {
                return "unknown/unknown";
            }
        }

        /// <summary>
        /// Listens for incoming connections and serves files in multiple threads.
        /// </summary>
        private void Listen()
        {
            // lst.IgnoreWriteExceptions = true;
            lst.Start();
            while (true)
            {
                try
                {
                    HttpListenerContext context = lst.GetContext();
                    Thread th = new Thread(() =>
                    {
                        Handle(context);
                    });
                    th.Start();
                }
                catch (HttpListenerException)
                {
                    // Expected when closing main window
                    logger.log("Unable to open Listener Context");
                }
            }
        }

        /// <summary>
        /// Handle a single request.
        /// </summary>
        private void Handle(HttpListenerContext context)
        {
            try
            {
                HttpListenerResponse response = context.Response;
                IDIDLSerializable target = null;

                if (context.Request.RawUrl != "/")
                {
                    char[] sep = { '/' };
                    string id = context.Request.RawUrl.Split(sep, System.StringSplitOptions.RemoveEmptyEntries)[0];
                    target = root.Find(id);
                }

                if (target != null)
                {
                    string path = target.ToString();
                    Console.WriteLine(path);

                    using (FileStream f = File.OpenRead(path))
                    {
                        response.ContentLength64 = f.Length;
                        //response.SendChunked = true;
                        //response.KeepAlive = true;
                        response.ContentType = GetMIMEType(path);
                        byte[] buffer = new byte[512 * 1024];
                        int read;
                        int total = 0;

                        while ((read = f.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            total += read;
                            Console.WriteLine((1.0 * total / f.Length).ToString());
                            Console.WriteLine(read);
                            response.OutputStream.Write(buffer, 0, read);
                        }

                        response.OutputStream.Close();
                        response.StatusCode = (int)HttpStatusCode.OK;
                        response.StatusDescription = "OK";

                        logger.log("OK " + context.Request.HttpMethod + " " + context.Request.RawUrl);
                    }
                }
                else
                {
                    /* 404 Not Found */
                    var msg = Encoding.UTF8.GetBytes("404 Not Found");
                    response.ContentLength64 = msg.Length;
                    response.StatusCode = (int)HttpStatusCode.NotFound;
                    response.StatusDescription = "NotFound";
                    using (Stream s = context.Response.OutputStream)
                        s.Write(msg, 0, msg.Length);

                    logger.log("404 " + context.Request.HttpMethod + " " + context.Request.RawUrl);
                }

                response.OutputStream.Close();
            }
            catch (Exception e) //TODO: refactor exception handling
            {
                Console.WriteLine(e);
            }
        }

        /// <summary>
        /// Start a separate thread listening for connections.
        /// </summary>
        public void Start()
        {
            handler = new Thread(() =>
            {
                Listen();
            });
            handler.Start();
        }

        public void Stop()
        {
            lst.Stop();
            handler.Abort();
        }
    }
}
